package mspbots.teams.client.core.data.groups;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import mspbots.teams.client.core.AbstractToolsUtil;
import mspbots.teams.client.core.BaseSyncService;
import mspbots.teams.client.oauth.TokenState;
import mspbots.teams.common.QueryParams;
import mspbots.teams.common.RabbitKeys;
import mspbots.teams.common.ResultMsg;
import mspbots.teams.common.ResultSync;
import org.springframework.stereotype.Service;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.net.URI;
import java.time.Duration;
import java.time.LocalDateTime;

/**
 * mspbots.data.teams.core.groups.GroupsService
 *
 * @author Alex bob(https://github.com/vnobo)
 * @date Created by 2020/6/3
 */
@Log4j2
@Service
@RequiredArgsConstructor
public class GroupsServiceImpl extends AbstractToolsUtil implements BaseSyncService {

    public Flux<JsonNode> find(Long tenantId) {
        return this.mergeGroups()
                .contextWrite(ctx -> ctx.put("tokenState", TokenState.withDefault(tenantId)));
    }

    public Flux<JsonNode> channels(Long tenantId, String groupsId) {
        return this.getChannels(groupsId)
                .contextWrite(ctx -> ctx.put("tokenState", TokenState.withDefault(tenantId)));
    }

    public Flux<JsonNode> members(Long tenantId, String groupsId) {
        return this.getMembers(groupsId)
                .contextWrite(ctx -> ctx.put("tokenState", TokenState.withDefault(tenantId)));
    }

    private Flux<JsonNode> getGroups() {
        QueryParams queryParams = QueryParams.withDefault();
        queryParams.setSelect("id,displayName,resourceProvisioningOptions");
        queryParams.setFilter("resourceProvisioningOptions/Any(x:x eq 'Team')");
        return this.teamsClient.fetch(UriComponentsBuilder.newInstance()
                .scheme("https").host("graph.microsoft.com").path("beta/groups")
                .queryParams(queryParams).build().toUri())
                .flatMap(jsonNode -> Flux.fromIterable(jsonNode.withArray("value")));
    }

    private Flux<JsonNode> getChannels(String groupsId) {

        URI uri = UriComponentsBuilder.newInstance().scheme("https").host("graph.microsoft.com")
                .path("beta/teams/{groupsId}/channels")
                .queryParam("$select", "id,displayName,description,email").build(groupsId);

        return this.teamsClient.fetch(uri)
                .flatMap(jsonNode -> Flux.fromIterable(jsonNode.withArray("value")));
    }

    private Flux<JsonNode> getMembers(String groupsId) {
        ResultSync resultSync = ResultSync.withDefault("GROUP-MEMBERS-SYNC");
        URI uri = UriComponentsBuilder.newInstance().scheme("https").host("graph.microsoft.com")
                .path("v1.0/groups/{id}/members").queryParams(QueryParams.withDefault()
                        .setSelect("id,displayName,mail")).build(groupsId);
        log.debug("Request members uri: [{}]", uri);
        return this.teamsClient.fetch(uri)
                .contextWrite(ctx -> ctx.put("resultSync", resultSync))
                .flatMap(jsonNode -> Flux.fromIterable(jsonNode.withArray("value")));
    }

    private Flux<JsonNode> mergeGroups() {
        return this.getGroups();
                /*.delayElements(Duration.ofMillis(200))
                .flatMap(groupNode -> this.getMembers(groupNode.findValue("id").textValue())
                        .onErrorResume(err -> Mono.just(this.objectMapper.createObjectNode()))
                        .collectList()
                        .map(members -> {
                            ObjectNode objectNode = groupNode.deepCopy();
                            objectNode.putPOJO("members", members);
                            return objectNode;
                        }));*/
    }

    @Override
    public Mono<Void> syncSlow(TokenState tokenState) {
        ResultSync resultSync = ResultSync.withDefault("GROUP-SYNC");
        return this.mergeGroups()
                .contextWrite(ctx -> ctx.put("tokenState", tokenState))
                .contextWrite(ctx -> ctx.put("resultSync", resultSync))
                .delayElements(Duration.ofSeconds(1))
                .doOnNext(groupNode -> this.syncGroupsChannel(groupNode.get("id").textValue(), tokenState))
                .doOnNext(groupNode -> this.sendEntity(RabbitKeys.GROUPS_SYNC_QUEUE_KEY, ResultMsg.builder()
                        .requestId(resultSync.getRequestId()).id(groupNode.get("id").textValue())
                        .action("none").entity(groupNode).tenantId(tokenState.getTenantId())
                        .syncDate(LocalDateTime.now()).build()))
                .then();
    }

    private void syncGroupsChannel(String groupId, TokenState tokenState) {
        ResultSync resultSync = ResultSync.withDefault("GROUP-CHANNELS-SYNC");
        this.getChannels(groupId)
                .contextWrite(ctx -> ctx.put("resultSync", resultSync))
                .contextWrite(ctx -> ctx.put("tokenState", tokenState))
                .map(channelNode -> {
                    ObjectNode objectNode = channelNode.deepCopy();
                    objectNode.put("channelId", channelNode.get("id").asText());
                    objectNode.put("groupId", groupId);
                    objectNode.put("tenantId", tokenState.getTenantId());
                    return objectNode;
                })
                .subscribe(resultNode -> this.sendEntity(RabbitKeys.GROUPS_CHANNEL_SYNC_QUEUE_KEY, ResultMsg.builder()
                                .requestId(resultSync.getRequestId()).id(resultNode.get("id").textValue())
                                .action("none").entity(resultNode).tenantId(tokenState.getTenantId())
                                .syncDate(LocalDateTime.now()).build()),
                        err -> log.error("Sync group channel err: {} ", err.getMessage()));
    }
}
